/*
    Space Rocks! Avoid the rocks as long as you can!
    Copyright (C) 2001  Paul Holt <pcholt@gmail.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/
#define EDITION " - The BLUBATS edition"
extern int font_height;
void clearBuffer();

// includes
#include <SDL/SDL_image.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#include <signal.h>

#include "config.h"
#include "SFont.h"
#include "sound.h"
#include "random_gen.h"
#include "bangdots.h"
#include "blubats.h"
#include "engine_exhaust.h"
#include "greeblies.h"
#include "highscore_io.h"
#include "mood_item.h"
#include "powerup.h"
#include "rockdodger_globals.h"
#include "rocks.h"
#include "spacedots.h"
#include "ship.h"
#include "datafun.h"

// constants
#define MAXROCKHEIGHT 100
#define M 255
#define BIG_FONT_FILE "20P_Betadance.png"

// macros
#define COND_ERROR(a) if ((a)) {initerror=strdup(SDL_GetError());return 1;}
#define NULL_ERROR(a) COND_ERROR((a)==NULL)


void handlesignal(int signal) {
  SDL_Quit();
  exit(128 + signal);
}

/*
 * The input states are names for mappings between input device states and game
 * inputs.
 */
enum inputstates { 
  UP,
  DOWN,
  LEFT,
  RIGHT,
  LASER,
  SHIELD,
  FAST,
  NOFAST,
  SCREENSHOT,
  _INPUTSTATESEND
}; 
#define NUM_INPUTS (_INPUTSTATESEND+1)

SDL_Joystick *joysticks[1];
#define JOYSTICK_DEADZONE 1024


// SDL_Surface global variables
SDL_Surface *surf_screen;	// Screen
SDL_Surface *surf_t_rock;	// Title element "rock"
SDL_Surface *surf_t_dodgers;	// Title element "dodgers"
SDL_Surface *surf_t_game;	// Title element "game"
SDL_Surface *surf_t_over;	// Title element "over"
SDL_Surface *surf_t_paused;	// Title element "paused"
SDL_Surface *surf_font_big;	// The big font
SDL_Surface *surf_font_small;	// The small font
SDL_Surface *surf_gauge;	// The raw gauge image
SDL_Surface *surf_gauge_shield[2];	// The shield gauge 0=on,1=off
SDL_Surface *surf_gauge_laser[2];	// The laser gauge 0=on,1=off


// Other global variables
char topline[1024];
char *initerror = "";
char name[1024];

float rockrate, rockspeed;

int nships;
float countdown = 0;
float level = 0;
int maneuver = 0;
int laser = 0;
int shieldsup = 0;
int oss_sound_flag = 0;
int scorerank = -1;		//minus one means no scorerank, we are not worthy
float fadetimer = 0, faderate;

int joystick_flag = 1;

int pausedown = 0, paused = 0;

int xoffset[NROCKS][MAXROCKHEIGHT];

char *sequence[] = {
  "Rock Dodger",
  "Press SPACE to start!",
  "http://sourceforge.net/projects/spacerocks/",
  "Arrow keys move the ship.",
  "Press S for shield and D for laser.",
  "If you need a pause press P.",
  "You can bounce off the sides of the screen.",
  "",
  "If you have a <500Mhz machine, tell me how this game performs!",
  "",
  "The little green guys are called Greeblies...",
  "Later in the game you will meet their friends...",
  "...the very dangerous Blubats!",
  "Watch out! Blubats may leave some droppings!",
  "",
  "Greetings go to: Jason Woofenden, Joshua Grams, Jack Beatmaster, ...",
  "... and of course to Paul Holt.",
  ""
};
const int nsequence = sizeof(sequence) / sizeof(char *);

extern char *optarg;
extern int optind, opterr, optopt;



// ************************************* FUNCS


void drawlaser() {
  int i, xc, hitrock, hitgreeb;
  RD_VIDEO_TYPE c, *rawpixel;

  if(laserlevel < 0)
    return;
  laserlevel -= movementrate;
  if(laserlevel < 0) {
    // If the laser runs out completely, there will be a delay before it can be brought back up
    laserlevel = -W / 2;
    return;
  }

  hitrock = -1;
  hitgreeb = -1;
  xc = xsize;
  // let xc = x coordinate of the collision between the laser and a space rock
  // 1. Calculate xc and determine the asteroid that was hit
  for(i = 0; i < MAX_ROCKS; i++) {
    if(rock[i].active) {
      if(yship + 12 > rock[i].y && yship + 12 < rock[i].y + rock[i].image->h
	 && xship + 32 < rock[i].x + (rock[i].image->w / 2)
	 && rock[i].x + (rock[i].image->w / 2) < xc) {
	xc = rock[i].x + (rock[i].image->w / 2);
	hitrock = i;
      }
    }
  }
  for(i = 0; i < MAX_GREEBLES; i++) {
    int greebheight = 16;
    int greebwidth = 16;
    struct greeble *g;
    g = &greeb[i];

    if(g->active) {
      int greebx = (int) g->x;
      int greeby = (int) g->y;
      if(g->landed) {
	greebx += rock[g->target_rock_number].x;
	greeby += rock[g->target_rock_number].y;
      }
      if(yship + 12 > greeby && yship + 12 < greeby + greebheight &&
	 xship + 32 < greebx + (greebwidth / 2) && greebx + (greebwidth / 2) <
	 xc) {
	xc = greebx + (greebwidth / 2);
	hitgreeb = i;
      }
    }

  }

  if(hitrock >= 0) {
    rock[hitrock].heat += movementrate * 3;
  }

  if(hitgreeb >= 0)
    kill_greeb(hitgreeb);

  if((hitgreeb = check_blubat_hit((int) yship + 12, xc)) >= 0) {
    kill_blubat(hitgreeb);
  }
  // Plot a number of random dots between xship and xsize
  SDL_LockSurface(surf_screen);
  rawpixel = (RD_VIDEO_TYPE *) surf_screen->pixels;
  c =
    SDL_MapRGB(surf_ship->format, rnd() * 128, 128 + rnd() * 120,
	       rnd() * 128);

  for(i = 0; i < (xc - xship) * 5; i += 10) {
    int x, y;
    x = rnd() * (xc - (xship + 32)) + xship + 32;
    y = yship + 12 + (rnd() - 0.5) * 1.5;
    rawpixel[surf_screen->pitch / 2 * y + x] = c;
  }

  SDL_UnlockSurface(surf_screen);
}

void drawdots(SDL_Surface * s) {
  int m;

  SDL_LockSurface(s);

  // Draw the background stars (aka space dots - they can't possibly be stars
  // because then we'd be moving past them at something like 10,000 times the
  // speed of light)
  draw_space_dots(s);

  // Draw the mood item...
  SDL_UnlockSurface(s);
  draw_mood_item(s);
  SDL_LockSurface(s);

  // Draw all the engine dots
  draw_engine_dots(s);

  // Create engine dots out the side we're moving from
  for(m = 0; m < 4; m++)
    if(maneuver & 1 << m)	// 'maneuver' is a bit field
      create_engine_dots2(5, m);
  // Create more engine dots comin out da back
  if(!gameover)
    create_engine_dots(250);

  // Draw all outstanding bang dots
  draw_bang_dots(s);

  SDL_UnlockSurface(s);
}

/*! \brief Set the SDL video mode
 *
 * This function sets the video mode and surf_screen. It will use the
 * fullscreen flag to decide if it is a fullscreen surface or not.
 *
 * \return the new surf_screen
 */
SDL_Surface *set_video_mode() {
  Uint32 flag;

  // Attempt to get the required video size
  flag = SDL_DOUBLEBUF | SDL_HWSURFACE;
  if(opt_fullscreen)
    flag |= SDL_FULLSCREEN;
  surf_screen = SDL_SetVideoMode(xsize, ysize, RD_VIDEO_BPP, flag);
  return surf_screen;
}


/*! \brief Toggle fullscreen mode
 *
 * Toggle between fullscreen and windowed mode. This will change some
 * global variables.
 */
void toggle_fullscreen() {
  opt_fullscreen = ~opt_fullscreen;
  set_video_mode();
}


int init() {
  int i;
  SDL_Surface *temp;

  signal(SIGINT, handlesignal);
  signal(SIGTERM, handlesignal);
  signal(SIGQUIT, handlesignal);

  read_high_score_table();

  data_dir = get_data_dir();

  if(oss_sound_flag) {

    // Initialise SDL with audio and video
    if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) != 0) {
      oss_sound_flag = 0;
      printf("Can't open sound, starting without it\n");
      atexit(SDL_Quit);
    } else {
      atexit(SDL_Quit);
      atexit(SDL_CloseAudio);
      oss_sound_flag = init_sound();
    }

  } else {
    // Initialise with video only
    COND_ERROR(SDL_Init(SDL_INIT_VIDEO) != 0);
    atexit(SDL_Quit);
  }

  // Attempt to initialize a joystick
  if(joystick_flag) {
    if(SDL_InitSubSystem(SDL_INIT_JOYSTICK) != 0) {
      joystick_flag = 0;
      printf("Can't initialize joystick subsystem, starting without it.\n");
    } else {
      int njoys;
      njoys = SDL_NumJoysticks();
      printf("%d joystick(s) detected\n", njoys);
      if(njoys == 0) {
	printf
	  ("That's not enough joysticks to start with joystick support\n");
	joystick_flag = 0;
      } else {
	joysticks[0] = SDL_JoystickOpen(0);
	if(joysticks[0] == NULL) {
	  printf("Couldn't open joystick %d\n", 0);
	  joystick_flag = 0;
	}
      }
    }
  }

  if(oss_sound_flag)
    play_tune(0);

  // Attempt to get the required video size
  set_video_mode();
  //TODO: Add a splash screen...
  //SDL_FillRect(surf_screen, NULL, SDL_MapRGB(surf_screen->format, 0xff, 0, 0));
  //SDL_Flip(surf_screen);

  // Set the title bar text
  SDL_WM_SetCaption("Rock Dodgers", "rockdodgers");

  NULL_ERROR(surf_screen);

  // Set the heat color from the range 0 (cold) to 300 (blue-white)
  for(i = 0; i < W * 3; i++)
    heatcolor[i] = SDL_MapRGB(surf_screen->format,
			      (i < W) ? (i * M / W) : (M),
			      (i < W) ? 0 : (i <
					     2 * W) ? ((i - W) * M / W) : M,
			      (i < 2 * W) ? 0 : ((i - W) * M / W)
			      // Got that?
      );

  // Load the Title elements
  NULL_ERROR(surf_t_rock = load_image("rock.png", 255, 0, 0));
  NULL_ERROR(surf_t_dodgers = load_image("dodgers.png", 255, 0, 0));

  NULL_ERROR(surf_t_game = load_image("game.png", 255, 0, 0));
  NULL_ERROR(surf_gauge = load_image("gauge.png", 0, 0, 0));

  NULL_ERROR(surf_gauge_laser[0] = IMG_Load(load_file("laser0.png")));
  NULL_ERROR(surf_gauge_laser[1] = IMG_Load(load_file("laser1.png")));
  NULL_ERROR(surf_gauge_shield[0] = IMG_Load(load_file("shield0.png")));
  NULL_ERROR(surf_gauge_shield[1] = IMG_Load(load_file("shield1.png")));

  NULL_ERROR(temp = IMG_Load(load_file("over.png")));
  SDL_SetColorKey(temp, SDL_SRCCOLORKEY | SDL_RLEACCEL,
		  (RD_VIDEO_TYPE) SDL_MapRGB(temp->format, 255, 0, 0));
  NULL_ERROR(surf_t_over = SDL_DisplayFormat(temp));

  NULL_ERROR(surf_t_paused = IMG_Load(load_file("paused.png")));

  surf_font_big = IMG_Load(load_file(BIG_FONT_FILE));
  InitFont(surf_font_big);
  //surf_font_small = IMG_Load(load_file(SMALL_FONT_FILE)); 
  // InitFont(surf_font_small);

  // Load the spaceship surface from the spaceship file
  temp = SDL_LoadBMP(load_file("ship.bmp"));
  NULL_ERROR(temp);
  SDL_SetColorKey(temp, SDL_SRCCOLORKEY | SDL_RLEACCEL,
		  (RD_VIDEO_TYPE) SDL_MapRGB(temp->format, 0, 255, 0));
  surf_ship = SDL_DisplayFormat(temp);
  NULL_ERROR(surf_ship);

  // Load the little spaceship surface from the spaceship file
  temp = SDL_LoadBMP(load_file("ship_small.bmp"));
  NULL_ERROR(temp);
  SDL_SetColorKey(temp, SDL_SRCCOLORKEY | SDL_RLEACCEL,
		  (RD_VIDEO_TYPE) SDL_MapRGB(temp->format, 0, 255, 0));
  surf_small_ship = SDL_DisplayFormat(temp);
  NULL_ERROR(surf_small_ship);

  NULL_ERROR(init_ship());
  init_engine_dots();
  init_space_dots(surf_screen);
  NULL_ERROR(init_blubats());
  NULL_ERROR(init_powerup());
  NULL_ERROR(init_mood_item());

  // Load all our lovely rocks
  for(i = 0; i < NROCKS; i++) {
    char a[100];

    sprintf(a, load_file("rock%d.bmp"), i);
    temp = SDL_LoadBMP(a);
    NULL_ERROR(temp);
    SDL_SetColorKey(temp, SDL_SRCCOLORKEY | SDL_RLEACCEL,
		    (RD_VIDEO_TYPE) SDL_MapRGB(temp->format, 0, 255, 0));
    surf_rock[i] = SDL_DisplayFormat(temp);
    NULL_ERROR(surf_rock[i]);

    sprintf(a, load_file("deadrock%d.bmp"), i);
    temp = SDL_LoadBMP(a);
    NULL_ERROR(temp);
    SDL_SetColorKey(temp, SDL_SRCCOLORKEY | SDL_RLEACCEL,
		    (RD_VIDEO_TYPE) SDL_MapRGB(temp->format, 0, 255, 0));
    surf_deadrock[i] = SDL_DisplayFormat(temp);
    NULL_ERROR(surf_deadrock[i]);
  }
  NULL_ERROR(init_greeblies());

  // Remove the mouse cursor
#ifdef SDL_DISABLE
  SDL_ShowCursor(SDL_DISABLE);
#endif

  return 0;
}


void showgauge(int x, SDL_Surface * surf[2], float fraction) {
  static int endx, w;
  static SDL_Rect src, dest;
  src.y = 0;
  if(fraction > 0) {
    if(fraction > 1)
      fraction = 1.0;
    src.x = 0;
    w = src.w = surf[0]->w * fraction;
    src.h = surf[0]->h;
    endx = src.w;
    dest.w = src.w;
    dest.h = src.h;
    dest.x = x;
    dest.y = ysize - src.h - 10;
    SDL_BlitSurface(surf[0], &src, surf_screen, &dest);
  } else {
    endx = 0;
    w = 0;
  }

  src.x = endx;
  src.w = surf[1]->w - w;
  src.h = surf[1]->h;
  dest.w = src.w;
  dest.h = src.h;
  dest.x = x + endx;
  dest.y = ysize - src.h - 10;
  SDL_BlitSurface(surf[1], &src, surf_screen, &dest);
}


void display_version_n_text() {
  char *text = "Version " VERSION EDITION "  (" COMPILEDATE ")";
  int x = (xsize - SFont_wide(text)) / 2 + sin(fadetimer / 4.5) * 10;
  PutString(surf_screen, x, ysize - 50 + sin(fadetimer / 2) * 5, text);

  text = sequence[(int) (fadetimer / 40) % nsequence];
  x = (xsize - SFont_wide(text)) / 2 + cos(fadetimer / 4.5) * 10;
  PutString(surf_screen, x, ysize - 100 + cos(fadetimer / 3) * 5, text);
  fadetimer += movementrate / 2.0;
}


/*! \brief Display de list o high scores mon.
 */
void display_highscores() {
  unsigned int i;
  char s[128];

  PutString(surf_screen, 180, 50, "High scores");
  for(i = 0; i < MAXIMUM_HIGH_SCORE_ENTRIES; ++i) {
    sprintf(s, "#%1d", i + 1);
    PutString(surf_screen, 150, 50 + (i + 2) * font_height, s);
    sprintf(s, "%4ld", high[i].score);
    PutString(surf_screen, 200, 50 + (i + 2) * font_height, s);
    sprintf(s, "%3s", high[i].name);
    PutString(surf_screen, 330, 50 + (i + 2) * font_height, s);
  }
}


/*! \brief Enter the highscore
 *
 * Called once per frame from draw() if we are in highscore enter
 * mode.
 */
void enter_highscore(void) {
  if(scorerank >= 0) {
    play_tune(2);
    if(SFont_Input
       (surf_screen, 330, 50 + (scorerank + 2) * font_height, 300, name)) {
      // Insert new high score and name into high score table
      high[scorerank].score = score;
      high[scorerank].name = strdup(name);
      high[scorerank].allocated = 1;
      scorerank = -1;		//we are unworthy again

      // Set the global name string to "", ready for the next winner
      name[0] = 0;

      // Change state to briefly show high scores page
      state = HIGH_SCORE_DISPLAY;
      state_timeout = 200;

      // Write the high score table to the file
      write_high_score_table();

      // Play the title page tune
      play_tune(0);
    }
  } else {
    state = HIGH_SCORE_DISPLAY;
    state_timeout = 400;
    play_tune(0);
  }
}

void draw_titlepage(void) {
  SDL_Rect src, dest;

  src.w = surf_t_rock->w;
  src.h = surf_t_rock->h;
  src.x = 0;
  src.y = 0;
  dest.w = src.w;
  dest.h = src.h;
  dest.x = (xsize - src.w) / 2 + cos(fadetimer / 6.5) * 10;
  dest.y = (ysize / 2 - src.h) / 2 + sin(fadetimer / 5) * 10;
  SDL_SetAlpha(surf_t_rock, SDL_SRCALPHA, (int) (170 + 85 * sin(fadetimer)));
  SDL_BlitSurface(surf_t_rock, &src, surf_screen, &dest);
  src.w = surf_t_dodgers->w;
  src.h = surf_t_dodgers->h;
  dest.w = src.w;
  dest.h = src.h;
  dest.x = (xsize - src.w) / 2 + sin(fadetimer / 6.5) * 10;
  dest.y =
    (ysize / 2 - src.h) / 2 + surf_t_rock->h + 20 +
    sin((fadetimer + 1) / 5) * 10;
  SDL_SetAlpha(surf_t_dodgers, SDL_SRCALPHA,
	       (int) (170 + 85 * sin(fadetimer - 1.0)));
  SDL_BlitSurface(surf_t_dodgers, &src, surf_screen, &dest);

  display_version_n_text();
}

void draw_gameover(void) {
  SDL_Rect src, dest;
  float fadegame, fadeover;

  if(fadetimer < 3.0 / faderate)
    fadegame = fadetimer / (3.0 / faderate);
  else
    fadegame = 1.0;

  if(fadetimer < 3.0 / faderate)
    fadeover = 0.0;
  else if(fadetimer < 6.0 / faderate)
    fadeover = ((3.0 / faderate) - fadetimer) / (6.0 / faderate);
  else
    fadeover = 1.0;

  src.w = surf_t_game->w;
  src.h = surf_t_game->h;
  src.x = 0;
  src.y = 0;
  dest.w = src.w;
  dest.h = src.h;
  dest.x = (xsize - src.w) / 2;
  dest.y = (ysize - src.h) / 2 - 40;
  SDL_SetAlpha(surf_t_game, SDL_SRCALPHA,
	       (int) (fadegame *
		      (200 + 55 * cos(fadetimer += movementrate / 1.0))));
  SDL_BlitSurface(surf_t_game, &src, surf_screen, &dest);

  src.w = surf_t_over->w;
  src.h = surf_t_over->h;
  dest.w = src.w;
  dest.h = src.h;
  dest.x = (xsize - src.w) / 2;
  dest.y = (ysize - src.h) / 2 + 40;
  SDL_SetAlpha(surf_t_over, SDL_SRCALPHA,
	       (int) (fadeover * (200 + 55 * sin(fadetimer))));
  SDL_BlitSurface(surf_t_over, &src, surf_screen, &dest);
}

/*! \brief Draw the black infinity of space
 *
 * The space, the final frontier....
 */
void draw_infinite_black() {
  float t;
  float l = level - (int) level;
  Uint32 c = 0;

  if(l > 0.98) {
    t = 4 * (1 - (1 - l) / (1 - 0.98));
    play_sound(4);
    switch ((int) t) {
    case 0:
      // ramp up red
      c = SDL_MapRGB(surf_screen->format, (int) (255 * t), 0, 0);
      break;
    case 1:
      // ramp down red, ramp up green
      c =
	SDL_MapRGB(surf_screen->format, (int) (255 * (1 - t)),
		   (int) (255 * (t - 1)), 0);
      break;
    case 2:
      // ramp down green, ramp up blue
      c =
	SDL_MapRGB(surf_screen->format, 0, (int) (255 * (3 - t)),
		   (int) (255 * (t - 3)));
      break;
    case 3:
      // ramp down blue
      c = SDL_MapRGB(surf_screen->format, 0, 0, (int) (255 * (4 - t)));
      break;
    }
  }
  SDL_FillRect(surf_screen, NULL, c);
}

/*! \brief Draw all
 *
 * Draw all the objects on the standard surface.
 *
 * \return bang=0 if everything ok, bang=1 if ship was hit.
 */ 
int draw() {
  int i;
  int bang;

  bang = 0;
  // Draw a fully black background
  draw_infinite_black();
  // Draw the background dots
  drawdots(surf_screen);

  // If it's firing, draw the laser
  if(laser) {
    drawlaser();
  } else {
    if(laserlevel < 3 * W) {
      laserlevel += movementrate / 4;
    }
  }

  // Draw ship
  if(!gameover && (state == GAME_PLAY || state == DEMO)) {
    draw_ship(surf_screen);
  }
  // Before the rocks are drawn, the greeblies are shown playing amongst them.
  if(rnd() < .02 * level * movementrate || state == TITLE_PAGE)
    activate_one_greeblie();
  move_all_greeblies();

  // Draw all the rocks, in all states. 
  display_rocks(surf_screen);
  // Draw all blubats
  display_blubats(surf_screen);

  switch (state) {
  case GAME_OVER:
    // If it's game over, show the game over graphic in the dead centre
    draw_gameover();
    break;
  case TITLE_PAGE:
    draw_titlepage();
    break;
  case HIGH_SCORE_ENTRY:
    enter_highscore();
  case HIGH_SCORE_DISPLAY:
    display_highscores();
    display_version_n_text();
  case GAME_PLAY:
    break;
  case SETUP: /*TODO: Do nothing for now! But later on there will be a
		screen for configuration and setup.*/
  case DEMO:
  case DEAD_PAUSE:
    break;			// handled below
  }

  if(!gameover && state == GAME_PLAY) {
    // Show the freaky shields
    if(initialshield > 0 || shieldsup && shieldlevel > 0) {
      draw_ship_shield(surf_screen);
    } else {
      // When the shields are off, check that the black points 
      // on the ship are still black, and not covered up by rocks
      bang = ship_check_collision(surf_screen);
    }

    //If in game display the laserpowerup
    if(powerup.active) {
      display_powerup(surf_screen);
      //Yes, shields protect against *everything*!
      if(!(initialshield > 0 || shieldsup && shieldlevel > 0)) {
	int bangpowerup = ship_check_collision(surf_screen);
	if(bangpowerup) {
	  if(powerup.surf == surf_laserpowerup) {
	    laserlevel += 16;
	    if(laserlevel > 3 * W)
	      laserlevel = 3 * W;
	  } else if(powerup.surf == surf_shieldpowerup) {
	    shieldlevel += 13;
	    if(shieldlevel > 3 * W)
	      shieldlevel = 3 * W;
	  } else {
	    puts("Illegal powerup!");
	    play_sound(4);
	  }
	  deactivate_powerup();
	  play_sound(6);
	}
      }
    }
  }

  update_bangdots(surf_screen);
  move_all_blubats();

  // Always draw the last score
  i = xsize - 250;
  snprintf(topline, 50, "Score: %ld", score);
  PutString(surf_screen, i, 0, topline);
  snprintf(topline, 50, "Level: %d", (int) level);
  PutString(surf_screen, i, 20, topline);

  // Draw all the little ships
  if(state == GAME_PLAY || state == DEAD_PAUSE || state == GAME_OVER) {
    draw_little_ships(nships, surf_screen);
    // Show the shield gauge
    showgauge(10, surf_gauge_shield, shieldlevel / (3 * W));
    // Show the laser gauge
    showgauge(200, surf_gauge_laser, laserlevel / (3 * W));
  }

  ticks_since_last = SDL_GetTicks() - last_ticks;
  last_ticks = SDL_GetTicks();
  if(ticks_since_last > 200 || ticks_since_last < 0) {
    movementrate = 0;
  } else {
    movementrate = ticks_since_last / 50.0;
    if(state == GAME_PLAY)
      inc_score(0, 0, SCORE_INCREMENT_FACTOR);	//This many points per screen update
  }
//#ifdef DEBUG
//    printf("last_ticks=%08X ticks_since_last=%02X movementrate=%10.4e\n", last_ticks, ticks_since_last, movementrate);
//#endif

  // Update the surface
  SDL_Flip(surf_screen);
  return bang;
}

void pausegame() {
  paused = !paused;

  SDL_Rect dest;
  // SDL_SetAlpha(surf_t_paused, SDL_SRCALPHA, 0);
  dest.w = surf_t_paused->w;
  dest.h = surf_t_paused->h;
  dest.x = (xsize - dest.w) / 2;
  dest.y = (ysize - dest.h) / 2;
  SDL_BlitSurface(surf_t_paused, NULL, surf_screen, &dest);
  // Update the surface
  SDL_Flip(surf_screen);
}

void unpausegame() {
  paused = 0;
  pausedown = 0;
}


void dead_pause() {
  play_tune(1);
  initialshield = 150;
  xship = 10;
  yship = ysize / 2;
  xvel = 2;
  yvel = 0;
  shieldlevel = 3 * W;
  laserlevel = 3 * W;
}


void game_over() {
  int i;

  clearBuffer();
  name[0] = 0;
  state_timeout = 5.0e6;
  
  if(score >= high[MAXIMUM_HIGH_SCORE_ENTRIES - 1].score) {
    // Read the high score table from the storage file
    read_high_score_table();
    
    // Find ranking of this score, store as scorerank
    for(i = 0; i < MAXIMUM_HIGH_SCORE_ENTRIES; ++i) {
      if(high[i].score <= score) {
	scorerank = i;
	break;
      }
    }
    
    // Lose the lowest name forever (loser!)
    if(high[MAXIMUM_HIGH_SCORE_ENTRIES - 1].allocated) {
      high[MAXIMUM_HIGH_SCORE_ENTRIES - 1].allocated = 0;
      free(high[MAXIMUM_HIGH_SCORE_ENTRIES - 1].name);
    }
    // Move all lower scores down a notch
    for(i = MAXIMUM_HIGH_SCORE_ENTRIES - 1; i > scorerank; --i) {
      high[i] = high[i - 1];
    }

    // Insert blank high score
    high[scorerank].score = score;
    high[scorerank].name = "";
    high[scorerank].allocated = 0;
  }
}


int gameloop() {
  int i = 0;
  Uint8 *keystate;
  int inputstate[NUM_INPUTS];
  SDL_Joystick *joystick;
  int stick_x, stick_y, button;


  for(;;) {
    if(!paused) {
      // Count down the game loop timer, and change state when it gets to zero or less;
      if((state_timeout -= movementrate * 3) < 0) {
	switch (state) {
	case DEAD_PAUSE:
	  // Create a new ship and start all over again
	  state = GAME_PLAY;
	  dead_pause(); 
	  deactivate_powerup();
	  break;
	case GAME_OVER:
	  state = HIGH_SCORE_ENTRY;
	  game_over();
	  break;
	case HIGH_SCORE_DISPLAY:
	  state = TITLE_PAGE;
	  state_timeout = 500.0;
	  break;
	case HIGH_SCORE_ENTRY:
	  // state = TITLE_PAGE;
	  // play_tune(1);
	  // state_timeout=100.0;
	  break;
#ifdef DEMO_ENABLED
	case TITLE_PAGE:
	  state = DEMO;
	  state_timeout = 100.0;
	  break;
	case DEMO:
	  state = HIGH_SCORE_DISPLAY;
	  state_timeout = 100.0;
	  break;
#else
	case TITLE_PAGE:
	  state = HIGH_SCORE_DISPLAY;
	  state_timeout = 200.0;
	  break;
	case DEMO:
	  break;		//to make compiler happy
#endif
	case SETUP:
	  break; //TODO: For now at least.
	case GAME_PLAY:
	  if(rnd() < (level - 3) / 777 && ((int)level) % 5 != 0) { //Every 5th level no blubats
	    activate_one_blubat();
	  }
	  break;
	}
      }

      if(level - (int) level < 0.8) {
	if(state != GAME_PLAY)
	  countdown -= 0.3 * movementrate;
	else
	  countdown -= rockrate * movementrate;

	while(countdown < 0) {
	  countdown++;
	  create_rock(rockspeed);
	}
      }
      // Increase difficulty
      if(state == GAME_PLAY)
	level += movementrate / 250;
      /* Old version from pads source:
       * rockspeed=5.0*xsize/640 + sqrt((int)level)/2.0;
       * rockrate = 0.2 + sqrt((int)level)/4.0;
       */
      // Debian version:
      rockspeed = 5.0 * xsize / 640 + sqrt((int) level) / 5.0;
      rockrate = 0.2 + sqrt((int) level) / 20.0;
      //greebrate = 0.2 + sqrt((int)level)/20.0;

      // Move all the rocks
      for(i = 0; i < MAX_ROCKS; i++)
	if(rock[i].active) {
	  rock[i].x += rock[i].xvel * movementrate;
	  rock[i].y += rock[i].yvel * movementrate;
	  if(rock[i].x < -32.0)
	    rock[i].active = 0;
	}
      // Move the powerup
      update_powerup();

      // FRICTION? In space? Oh well.
      xvel *= pow((double) 0.9, (double) movementrate);
      yvel *= pow((double) 0.9, (double) movementrate);
      // if (abs(xvel)<0.00001) xvel=0;
      // if (abs(yvel)<0.00001) yvel=0;

      // INERTIA
      xship += xvel * movementrate;
      yship += yvel * movementrate;

      // BOUNCE X  (okay, throwing all pretense of realism out the
      // window)
      if(xship < 0 || xship > xsize - surf_ship->w) {
	// BOUNCE from left and right wall
	xship -= xvel * movementrate;
	xvel *= -0.99;
      }
      // BOUNCE Y
      if(yship < 0 || yship > ysize - surf_ship->h) {
	// BOUNCE from top and bottom wall
	yship -= yvel * movementrate;
	yvel *= -0.99;
      }


      if(draw() && state == GAME_PLAY) {
	if(oss_sound_flag) {
	  // Play the explosion sound
	  play_sound(0);
	}
	makebangdots(xship, yship, xvel, yvel, surf_ship, 30, 3);
	if(--nships <= 0) {
	  gameover = 1;
	  state = GAME_OVER;
	  state_timeout = 200.0;
	  fadetimer = 0.0;
	  faderate = movementrate;
	  level = floor(level); /*Otherwise if level is >x.98 and
				  smaller then x+1 the function
				  draw_infinite_black() will cause
				  problems. It will loop the sound and
				  will not black the background.*/
	  //Remove blubats
	  deactivate_all_blubats();
	} else {
	  state = DEAD_PAUSE;
	  state_timeout = 100.0;
	}
      }

      SDL_PumpEvents();
      keystate = SDL_GetKeyState(NULL);

      if(state != HIGH_SCORE_ENTRY
	 && (keystate[SDLK_q] || keystate[SDLK_ESCAPE])) {
	return 0;
      }
#ifdef CHEAT
      if(keystate[SDLK_5]) {
	inc_score(0, 0, 10000);
	level++;
      }
#endif

      if(keystate[SDLK_SPACE]
	 && (state == HIGH_SCORE_DISPLAY || state == TITLE_PAGE
	     || state == DEMO)) {

	for(i = 0; i < MAX_ROCKS; i++)
	  rock[i].active = 0;
	deactivate_greeblies();

	rockspeed = 5.0 * xsize / 640;

	nships = 4;
	clear_score();
	level = 1;

	state = GAME_PLAY;
	play_tune(1);

	xvel = -1;
	gameover = 0;
	yvel = 0;
	xship = 0;
	yship = ysize / 2;
	shieldlevel = 3 * W;
	laserlevel = 3 * W;
	initialshield = 150;

	play_sound(4);

      }

      maneuver = 0;
      laser = 0;
    } else {
      // SDL_Event e;
      // SDL_PollEvent(&e);
      SDL_PumpEvents();
      keystate = SDL_GetKeyState(NULL);
      // if (keystate[SDLK_PAUSE]) printf ("p\n");
    }

    if(state == GAME_PLAY) {
      if(!gameover) {
	if(!paused) {
	  for(i = 0; i < NUM_INPUTS; i++) {
	    inputstate[i] = 0;
	  }
	  if(joystick_flag == 1) {
	    joystick = joysticks[0];
	    stick_x = SDL_JoystickGetAxis(joystick, 0);
	    stick_y = SDL_JoystickGetAxis(joystick, 1);

	    if(stick_x > JOYSTICK_DEADZONE) {
	      inputstate[RIGHT] |= 1;
	    }
	    if(stick_x < -JOYSTICK_DEADZONE) {
	      inputstate[LEFT] |= 1;
	    }
	    if(stick_y > JOYSTICK_DEADZONE) {
	      inputstate[DOWN] |= 1;
	    }
	    if(stick_y < -JOYSTICK_DEADZONE) {
	      inputstate[UP] |= 1;
	    }

	    button = SDL_JoystickGetButton(joystick, 0);
	    if(button == 1) {
	      inputstate[LASER] |= 1;
	    }

	    button = SDL_JoystickGetButton(joystick, 1);
	    if(button == 1) {
	      inputstate[SHIELD] |= 1;
	    }
	  }
	  if(keystate) {
	    if(keystate[SDLK_UP]) {
	      inputstate[UP] |= 1;
	    }
	    if(keystate[SDLK_DOWN]) {
	      inputstate[DOWN] |= 1;
	    }
	    if(keystate[SDLK_LEFT]) {
	      inputstate[LEFT] |= 1;
	    }
	    if(keystate[SDLK_RIGHT]) {
	      inputstate[RIGHT] |= 1;
	    }
	    if(keystate[SDLK_d]) {
	      inputstate[LASER] |= 1;
	    }
#ifdef CHEAT
	    if(keystate[SDLK_1]) {
	      inputstate[FAST] |= 1;
	    }
	    if(keystate[SDLK_2]) {
	      inputstate[NOFAST] |= 1;
	    }
#endif
	    if(keystate[SDLK_3]) {
	      inputstate[SCREENSHOT] |= 1;
	    }
	    if(keystate[SDLK_s]) {
	      inputstate[SHIELD] |= 1;
	    }
	  }


	  if(inputstate[UP]) {
	    yvel -= 1.5 * movementrate;
	    maneuver |= 1 << 3;
	  }
	  if(inputstate[DOWN]) {
	    yvel += 1.5 * movementrate;
	    maneuver |= 1 << 1;
	  }
	  if(inputstate[LEFT]) {
	    xvel -= 1.5 * movementrate;
	    maneuver |= 1 << 2;
	  }
	  if(inputstate[RIGHT]) {
	    xvel += 1.5 * movementrate;
	    maneuver |= 1;
	  }
	  if(inputstate[LASER]) {
	    laser = 1;
	  }
	  if(inputstate[FAST]) {
	    fast = 1;
	  }
	  if(inputstate[NOFAST]) {
	    fast = 0;
	  }
	  if(inputstate[SCREENSHOT]) {
	    SDL_SaveBMP(surf_screen, "snapshot.bmp");
	  }
	  shieldsup = inputstate[SHIELD];
	}

	if(keystate && (keystate[SDLK_PAUSE] || keystate[SDLK_p])) {
	  if(!pausedown) {
	    pausegame();
	    pausedown = 1;
	  }
	} else {
	  pausedown = 0;
	}

      } else {
	shieldsup = 0;
	unpausegame();
      }
    } else if(state == TITLE_PAGE) {
      if(keystate[SDLK_F9])
	toggle_fullscreen();
    }

    // DEBUG mode to slow down the action, and see if this game is playable on a 486
    if(fast)
      SDL_Delay(100);
  }
}


int main(int argc, char **argv) {
  int i, x;

  oss_sound_flag = 1;
  while((x = getopt(argc, argv, "wskhx:y:")) >= 0)
    switch (x) {
    case 'x':
      xsize = atoi(optarg);
      printf("xsize %d\n", xsize);
      break;
    case 'y':
      ysize = atoi(optarg);
      printf("ysize %d\n", ysize);
      break;
    case 's':
      oss_sound_flag = 0;
      break;
    case 'k':
      joystick_flag = 0;
      break;
    case 'w':
      opt_fullscreen = 0;
      break;
    case 'h':
      printf("Rock Dodgers\n"
	     "  -h This help message\n"
	     "  -f Full screen\n"
	     "  -w Window mode\n"
	     "  -x xsize\n"
	     "  -y ysize\n"
	     "  -k Keyboard only - disable joystick\n"
	     "  -s Silent mode (no OSS sound)\n");
      exit(0);
      break;
    }

  initrnd();
  if(init()) {
    printf("Cannot start: '%s'\n", initerror);
    return 1;
  }

  while(1) {
    for(i = 0; i < MAX_ROCKS; i++)
      rock[i].active = 0;
    rockspeed = 5.0 * xsize / 640;
    initticks = SDL_GetTicks();
    if(gameloop() == 0)
      break;
    SDL_Delay(1000);
  }
  SDL_Quit();
  shutdown_blubats();
  return 0;
}
